package ca.concordia.comp6231.server;

import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import ca.concordia.comp6231.record.CriminalRecord;
import ca.concordia.comp6231.record.MissingRecord;
import ca.concordia.comp6231.record.Record;
import ca.concordia.comp6231.udp.UDPClient;
import ca.concordia.comp6231.udp.UDPService;

public class StationServer implements StationServerInterface{
	private String serverName;
	ServerConfiguration config;
	Thread udpService;
	UDPClient udpClient;
	
	private ConcurrentHashMap<String, List<Record>> records;
	private ConcurrentHashMap<String, Integer> currentID;
	
	public StationServer(String serverName){
		config = new ServerConfiguration(serverName);
		this.setServerName(serverName);
		
		records = new ConcurrentHashMap<String, List<Record>>();
		
		currentID = new ConcurrentHashMap<String, Integer>();
		currentID.put("MR", 9999);
		currentID.put("CR", 9999);
		
		loadRMIService();
		loadUDPService();
	}
	
	private void loadRMIService(){
		int portNo = config.getPortNo();
		try {
			exportServer(portNo);
			System.out.println("[" + new Date(System.currentTimeMillis()) + "]" + 
					" Server: " + getServerName() + " is up and running at port: " + 
					portNo + ".");
		} catch (Exception e) {
			System.out.println("[" + new Date(System.currentTimeMillis()) + "]" + 
					" Server: " + getServerName() + " failed to start at port: " + 
					portNo + ".");
		}
	}
	
	private void loadUDPService(){
		int udpPortNo = config.getUDPPortNo();
		udpService = new Thread(new UDPService(udpPortNo, this));
		
		try{
			udpService.start();
			System.out.println("[" + new Date(System.currentTimeMillis()) + "]" + 
					" Server: " + getServerName() + " started running UDP service at port: " + 
					udpPortNo + ".");
		}catch(Exception e){
			System.out.println("[" + new Date(System.currentTimeMillis()) + "]" + 
					" Server: " + getServerName() + " failed to start UDP service at port: " + 
					udpPortNo + ".");
		}		
	}
	
	public String sendUDPMessage(String message, String hostName, int portNo){
		if(udpClient == null){
			udpClient = new UDPClient();
		}
		return udpClient.sendUDPMessage(message, hostName, portNo);
	}
	
	@Override
	public void createCRecord(String fName, String lName, String crimeDesc, String status){
		String ID = generateID("CR");
		CriminalRecord record = new CriminalRecord(ID, fName, lName, crimeDesc, status);
		String firstLetterOfLastName = lName.substring(0, 1).toUpperCase();
		addNewRecord(firstLetterOfLastName, record);
	}
	
	@Override
	public void createMRecord(String fName, String lName, String address, String lastDate,
							String lastLocation, String status){
		
		String ID = generateID("MR");
		MissingRecord record = new MissingRecord(ID, fName, lName, address, lastDate, lastLocation, status);
		String firstLetterOfLastName = lName.substring(0, 1).toUpperCase();
		addNewRecord(firstLetterOfLastName, record);
	}
	
	@Override
	public String getRecordCounts(){
		String response = this.getServerName() + " " + getRecordCountForCurrentServer();
		
		List<String> serverNames = config.getAvailableServerNames();
		for(String server:serverNames){
			if(!server.equals(this.getServerName())){
				String serverResponse = sendUDPMessage("getRecordCount", 
														config.getHostName(server), 
														config.getUDPPortNo(server));
				System.out.println(serverResponse);
				if(!serverResponse.equals("Unknown request")){
					response += "," + serverResponse.trim();
				}
			}
		}
		
		return response;
	}
	
	public int getRecordCountForCurrentServer(){
		int total = 0;
		for (List<Record> list : records.values()) {
		    total += list.size();
		}
		
		return total;
	}
	
	@Override
	public void editRecord(String lName, String recordID, String newStatus){
		String firstLetterOfLastName = lName.substring(0, 1).toUpperCase();
		if(!records.containsKey(firstLetterOfLastName)){
			//throw Exception
		}
		
		List<Record> recordList = records.get(firstLetterOfLastName);
		int index = findIndexOfRecordByRecordIDAndLastName(recordList, recordID, lName);
		
		if(index < 0){
			//throw Exception
		}
		
		recordList.get(index).setStatus(newStatus);
		
		records.put(firstLetterOfLastName, recordList);
	}
	
	private int findIndexOfRecordByRecordIDAndLastName(List<Record> recordList, String recordID, String lastName){
		int i = 0;
		for(Record r:recordList){
			if(r.getRecordID().equalsIgnoreCase(recordID)  && r.getLastName().equalsIgnoreCase(lastName)){
				return i;
			}else{
				++i;
			}
		}
		
		return -1;
	}
	
	@Override
	public void printAllRecords(){
		System.out.println("[" + new Date(System.currentTimeMillis()) + "] Server: " + getServerName());
		for(Entry<String, List<Record>> e : records.entrySet()){
			System.out.println(e.getKey()+"="+e.getValue());
		}
		System.out.println("Total no:" + getRecordCounts());
	}

	public String getServerName() {
		return serverName;
	}

	public void setServerName(String serverName) {
		this.serverName = serverName;
	}
	
	private String generateID(String type){
		int lastID = currentID.get(type);
		currentID.put(type, ++lastID);
		
		return type + lastID;
	}
	
	private void addNewRecord(String key, Record newRecord){
		List<Record> recordList = getRecordList(key);
		recordList.add(newRecord);
		records.put(key, recordList);
	}
	
	private List<Record> getRecordList(String key){
		List<Record> recordList;
		if(records.containsKey(key)){
			recordList = records.get(key);
		}else{
			recordList = new ArrayList<Record>();
		}
		
		return recordList;
	}
	
	public void exportServer(int portNo) throws Exception{
		Remote obj = UnicastRemoteObject.exportObject(this, portNo);
		Registry r = LocateRegistry.createRegistry(portNo);
		r.bind(serverName, obj);
	}
}
